本文为个人学习Catlike Coding的教程系列Custom SRP时的学习笔记
Custom Render Pipeline - Taking Control of Rendering
搭建了渲染不透明、天空盒、半透明物体以及错误渲染物体的基本渲染管线
此时会发现调用到的API与RenderFeature中的相差无几,或许RenderFeature其实就是作为管线的补充内容。
使用C#的partial关键字对类进行分割
catlike使用partial关键字将CameraRenderer类进行了拆分,将Editor模式时才运行的功能拆分了出去,
然后使用#if UNITY_EDITOR
以及#endif
包含了Editor模式时才运行的代码。
具体可参考官方文档
一次DrawRenderers可包含多种ShaderTagId的物体
写法如下
1 | static ShaderTagId[] legacyShaderTagIds = { |
就是给DrawingSetting调用SetShaderPassName以增加更多的Pass种类
这节做完时的效果
2021.06.18
Draw Calls - Shaders and Batches
这一节主要是写Shader以及实现批处理,是个人比较熟悉的内容,没什么特别好讲的,但其中有一处细节是第一次知道。
关于Unity各种批处理的详细内容也可以看此文:相关文档
Clear Buffer也是一次DrawCall
第一节中有提到如果Clear的位置在SetUpCameraProperties之前,则会比较低效地调用Shader进行Clear,因此本质上算是一次绘制,这也是比较好理解的
但之后的快速Clear也是作为一次DrawCall,这在第二节中被提及:
姑且作为一个小知识记下。
这节做完时的效果
2021.06.19
Directional Lights - Direct Illumination
这一节主要是实现Directional Light以及BRDF,也就是PBR的直接光照部分,大部分也是我比较熟悉的内容
关于法线插值与归一化
在fragment shader时需要对法线进行归一化也是老生常谈的基础内容,不过个人还是第一次可视化地直观看见artifact,还是挺有趣的:
NativeArray
Catlike使用NativeArray来储存灯光数据
It’s a struct that acts like an array, but provides a connection to a native memory buffer.** It makes it possible to efficiently share data between managed C# code and the native Unity engine code.**
简单来说就是相比普通的数组,它在C#与Unity底层代码之间传递效率更高。
NativeArray官方文档
1 | void SetupLights() |
就用法上而言可以说跟数组相差无几
Shader GUI
其实这是个人第一次做Shader GUI相关的内容,做过一次之后发现其实跟Editor拓展编辑器极为神似,Unity在这方面还是挺方便的。
总之,在Shader结尾写上CustomEditor "CustomShaderGUI"
然后将CustomShaderGUI.cs放入Editor文件夹下表示是个Editor程序即可
最关键的就是调用OnGUI()
函数
1 | public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) |
其对Property的变量进行赋值的做法如下:
1 | bool SetProperty(string name, float value) |
是Keyword的就增加个Keyword值设置,同时还要将Property的float进行修改。
这节做完的效果
2021.06.21
Directional Shadows - Cascaded Shadow Maps
这一节内容相当多,阴影的原理虽然早就了解,但是实现起来还是比较繁琐复杂。
万能的CullingResults
在前几节的时候还觉得CullingResults类就是单纯的储存被裁剪后的物体的类,但后来发现这个类能做的事情相当多,且与Shadow Map的实现强相关。
1 | cullingResults.ComputeDirectionalShadowMatricesAndCullingPrimitives( |
可通过它自带的ComputeDirectionalShadowMatricesAndCullingPrimitives()
获取VP矩阵以及SplitData,可以说有这几项内容之后普通的ShadowMap基本就实现完了……
而这个cullingResults的获取也十分简单(取自CameraRenderer.cs):
1 | bool Cull (float maxShadowDistance) { |
最后调用context.DrawShadows进行shadowmap的绘制:
1 | var shadowSettings = new ShadowDrawingSettings(cullingResults, light.visibleLightIndex); |
可发现这里也还是有cullingResults的存在
Shadow Texture的一些特殊设置
Shadow Texture从它刚申请来的时候就是那么的与众不同:
1 | buffer.GetTemporaryRT( |
奢华的32bit,还有Unity根据平台自适应的TextureFormat。
1 | buffer.SetRenderTarget( |
This enum describes what should be done on the render target when it is activated (loaded).
When the GPU starts rendering into a render target, this setting specifies the action that should be performed on the existing contents of the surface. Tile-based GPUs may get performance advantage if the load action is Clear or DontCare. The user should avoid using RenderBufferLoadAction.Load whenever possible.
Please note that not all platforms have load/store actions, so this setting might be ignored at runtime. Generally mobile-oriented graphics APIs (OpenGL ES, Metal) take advantage of these settings.
设置为DontCare就是说读取这个RenderBuffer时不对它的内容进行任何处理
Shadow Pancaking
由于ShadowCaster渲染时,相机的near plane会尽可能地靠近物体以提高精度,但这也可能导致有些物体的个别顶点比near plane更加靠近相机而被剔除
然而当物体本身就交错于near plane两边时,还是会存在artifact,这是就要将near plane进行后(向相机)移
Culling Bias
由于Cascade Shadow Map会在Shadow Texture上多次绘制同一物体,为了减少Draw Call,就尽可能剔除已经在较低层级的Cascade中绘制过了的物体,因此有了Culling Bias的概念。
这节做完的效果
2021.06.23
Baked Light - Light Maps and Probes
Light Probe Proxy Volumes
由于光照探针是逐物体使用(一个物体对应一个探针的信息),因此若物体过大,其间接光照信息便容易出错,此时便可以使用Light Probe Proxy Volumes,简称LPPVs。
但Catlike提到,此时探针只会使用L1SH的信息,因此会显得精度较低。这也是能够理解的,毕竟是动态更新,应当尽量减少计算量。
Meta Pass
因为间接漫射光应该受到这些物体表面的反射率的影响,因此需要新定义一个Meta Pass,用来补完烘焙间接光的颜色。
其中最重要的就是Fragment:
1 | float4 MetaPassFragment(Varyings input): SV_TARGET |
其实与普通Pass的Frag没什么特别的区别
这节做完时的效果
2021.06.27
Shadow Masks - Baking Direct Occlusion
这一节内容较少,主要还是实现Shadow Mask以及Distance Shadow Mask
逻辑或运算符 |
CameraRenderer
中有这么一段写法,我一直很在意其中"|"的用法:
1 | var drawingSettings = new DrawingSettings( |
而PerObjectData
是一个枚举类型:
1 | public enum PerObjectData |
每个值都是2的n次方
官方文档中如此解释"|":
也就是说,对于c中每一个二进制位,如果ab两者中对应的二进制位有一个是1,则这个二进制位值即为1
如果再形象一些表达,就是一排电闸,每个电闸对应一盏灯,上面perObjectData
的赋值其实就是打开对应灯的开关。
还是挺有意思的写法。
关于矢量值的通道选择
在进行多光源Shadowmask采样时需要对通道进行选择,Catlike此处使用float4 value[channel]
的写法来获取对应的通道值,他也解释了为什么不是手动传入一个float4值来点乘获取(如获取r通道值则传入float4(1, 0, 0, 0)来dot)。
实际上value[channel]的写法,GPU会帮我们编译成之前所说的传入对应通道值为1的float4值进行点乘,如果我们还写成点乘,则还得额外设置一个float4数组,反而变得麻烦了。
这节做完时的效果
2021.06.29
LOD and Reflections - Adding Details
这节主要就是实现LOD和反射探针,内容也比较少。
LOD Bias
已知LOD是根据距离判断物体所占屏幕比例大小,来进行模型和贴图的切换以减少渲染开销。而LOD Bias则是对这一比例进行调整:
LOD Bias = 2.0(默认值)
LOD Bias = 1.0
可以看出其实就是单纯在比例上增加一个乘法。
如果更感性来说,就是LOD Bias越大,对设备性能要求越高,LOD Bias越低则设备性能要求越低。
LOD Cross Fade
由于单纯进行LOD模型的切换会导致跳变,导致观感较差,因此在切换时也要追求自然,便有了Cross Fade
Cross Fade其实就是对进行切换的两级LOD模型都进行渲染,此时在Shader里通过Clip来进行切换,写法如下:
1 | void ClipLOD (float2 positionCS, float fade) { |
通过算法可以看出,正在消隐的LOD模型,fade
值会大于0且不断减小,而正在出现的LOD模型fade
值则小于零而不断增加。
倒是其中调用的InterleavedGradientNoise
函数非常值得挖掘,它被定义在srp自带的Random.hlsl中:
1 | //From Next Generation Post Processing in Call of Duty: Advanced Warfare [Jimenez 2014] |
Animated Cross-Fading
如果只用了Cross Fade而没有开启Animated Cross-Fading,那么当摄像机与物体距离正好在两个LOD层级切换的阈值之间时,便会看见物体上的切换Noise:
显然我们并不希望玩家看清楚这些玩意,而Animated Cross-Fading便是为此而开发。
如果摄像机达到了LOD切换的距离阈值,那么LOD Group就会用半秒(即LODGroup.crossFadeAnimationDuration
的默认值)的时间在两个LOD层级之间切换,不会出现玩家停留在某一距离,而LOD切换也停留在一半的情况。
这节完成时的效果
2021.06.30
Complex Maps - Masks, Details, and Normals
这节就是用上各种Texture,没什么特别值得讲的内容
其中提及的相对陌生一些的内容应当也就是关于纹理压缩格式了
DXT5
根据压缩格式的不同,采用使用的函数也不一样,DXT5(即BC3)会将原本R通道的值移到A通道(8bits),G通道不变(6bits),其余两通道则为5bits。
2021.08.09
to be continue
感谢您阅读完本文,若您认为本文对您有所帮助,可以将其分享给其他人;若您发现文章对您的利益产生侵害,请联系作者进行删除。